/**
 * 
 */
package no.netlight.gotogate;

import java.util.ArrayList;
import java.util.List;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Transaction;
import org.neo4j.index.IndexService;

/**
 * 
 *  @author Jorunn Melby
 */
public class RouteGenerator {

	private Node root;
	private IndexService index;
	private GraphConstructor db;
	
	public RouteGenerator(GraphConstructor db){
		this.db = db;
		this.index = db.getIndex();
	}

	/**
	 * 
	 * @param start
	 *            The name of the starting station
	 * @param goal
	 *            The name of the goal station
	 * @param flightTime
	 *            Flight time as "hhmm"
	 * @param transferTime
	 *            Specifies how much time the user will have between transfers.
	 *            In minutes.
	 */
	public void printAllRoutes(String start, String goal, String flightTime,
			int transferTime) {

		Transaction tx = db.beginTx();

		try {

			root = index.getSingleNode("name", start);
			Node end = index.getSingleNode("name", goal);
			
			if(root == null || end == null){
				//the input is wrong
				return;
			}

			AllSimplePaths asp = new AllSimplePaths(root, end,
					MyRelationshipTypes.GOESTO, Direction.OUTGOING);
			List<List<PropertyContainer>> nodeList = asp.getPaths(15);

			int totalTime; // in minutes
			int numberOfTransfers;
			int currentIndex;
			String previousTransportationType;
			String previousArrivalTime;
			String currentTime;
			ArrayList<Route> routes = new ArrayList<Route>();
			for (List<PropertyContainer> path : nodeList) {
				totalTime = 0;
				currentTime = flightTime;
				numberOfTransfers = 0;
				previousTransportationType = null;
				// indices specifies which departure and arrival time to use in
				// the list in the relation
				ArrayList<Integer> indices = new ArrayList<Integer>();
				// traverse path starting from the goal to ensure arrival before
				// the flight departs
				for (int i = path.size() - 1; i >= 0; i--) {
					PropertyContainer entity = path.get(i);
					if (entity.hasProperty("arivaltimes")) {
						if (previousTransportationType != null
								&& !previousTransportationType.equals(entity
										.getProperty("name"))) {
							// transfer required
							numberOfTransfers++;
						}
						previousTransportationType = (String) entity
								.getProperty("name");
						currentIndex = -1;
						String[] arrivalTimes = (String[]) entity
								.getProperty("arivaltimes");
						// find the latest possible arrival time, to minimize
						// waiting
						for (int j = 0; j < arrivalTimes.length; j++) {
							if (Integer.parseInt(arrivalTimes[j]) > Integer
									.parseInt(addTime(currentTime,
											-transferTime))) {
								break;
							} else {
								currentIndex = j;
							}
						}
						// invalid route
						if (currentIndex == -1) {
							totalTime = -1;
							numberOfTransfers = -1;
							break;
						}
						previousArrivalTime = ((String[]) entity
								.getProperty("arivaltimes"))[currentIndex];
						// have to wait at the station/airport; include in
						// totalTime
						if (previousArrivalTime != null) {
							totalTime += getTimeDifference(previousArrivalTime,
									currentTime);
						}
						// the time to consider is now the departure time at the
						// next station
						currentTime = (((String[]) entity
								.getProperty("departuretimes"))[currentIndex]);
						// add the transportation time for this route segment
						totalTime += getTimeDifference(currentTime,
								previousArrivalTime);
						indices.add(currentIndex);
					}
				}
				// route is valid
				if (totalTime != -1) {
					// insert route, organized by totalTime
					insertRoute(routes, new Route(path, indices, totalTime,
							numberOfTransfers));
				}
			}
			// print all alternatives
			for (int i = 0; i < routes.size(); i++) {
				System.out.println("Route Alternative "
						+ Integer.toString(i + 1));
				System.out.println(routes.get(i).returnRouteInfo());
				System.out.println();
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}


	public Route[] getAllRoutes(String start, String goal, String flightTime,
			int transferTime) {

		Transaction tx = db.beginTx();
		ArrayList<Route> routes = new ArrayList<Route>();
		try {

			root = index.getSingleNode("name", start);
			Node end = index.getSingleNode("name", goal);

			if(root == null || end == null){
				//the input is wrong and we do nothing
				return null;
			}
			
			AllSimplePaths asp = new AllSimplePaths(root, end,
					MyRelationshipTypes.GOESTO, Direction.OUTGOING);
			List<List<PropertyContainer>> nodeList = asp.getPaths(15);

			int totalTime; // in minutes
			int numberOfTransfers;
			int currentIndex;
			String previousTransportationType;
			String previousArrivalTime;
			String currentTime;
			
			for (List<PropertyContainer> path : nodeList) {
				totalTime = 0;
				previousArrivalTime = null;
				currentTime = flightTime;
				numberOfTransfers = 0;
				previousTransportationType = null;
				// indices specifies which departure and arrival time to use in
				// the list in the relation
				ArrayList<Integer> indices = new ArrayList<Integer>();
				// traverse path starting from the goal to ensure arrival before
				// the flight departs
				for (int i = path.size() - 1; i >= 0; i--) {
					PropertyContainer entity = path.get(i);
					if (entity.hasProperty("arivaltimes")) {
						if (previousTransportationType != null
								&& !previousTransportationType
										.equals((String) entity
												.getProperty("name"))) {
							// transfer required
							numberOfTransfers++;
						}
						previousTransportationType = (String) entity
								.getProperty("name");
						currentIndex = -1;
						String[] arrivalTimes = (String[]) entity
								.getProperty("arivaltimes");
						// find the latest possible arrival time, to minimize
						// waiting
						for (int j = 0; j < arrivalTimes.length; j++) {
							if (Integer.parseInt(arrivalTimes[j]) > Integer
									.parseInt(addTime(currentTime,
											-transferTime))) {
								break;
							} else {
								currentIndex = j;
							}
						}
						// invalid route
						if (currentIndex == -1) {
							totalTime = -1;
							numberOfTransfers = -1;
							break;
						}
						previousArrivalTime = ((String[]) entity
								.getProperty("arivaltimes"))[currentIndex];
						// have to wait at the station/airport; include in
						// totalTime
						if (previousArrivalTime != null) {
							totalTime += getTimeDifference(previousArrivalTime,
									currentTime);
						}
						// the time to consider is now the departure time at the
						// next station
						currentTime = (((String[]) entity
								.getProperty("departuretimes"))[currentIndex]);
						// add the transportation time for this route segment
						totalTime += getTimeDifference(currentTime,
								previousArrivalTime);
						indices.add(currentIndex);
					}
				}
				// route is valid
				if (totalTime != -1) {
					// insert route, organized by totalTime
					insertRoute(routes, new Route(path, indices, totalTime,
							numberOfTransfers));
				}
			}
			
			tx.success();
		} finally {
			tx.finish();
		}
		if(routes.size() == 0){
			return null;
		}
		return routes.toArray(new Route[routes.size()]);
	}
	/**
	 * This method inserts the route into the list such that the list is sorted
	 * by the total transportation time.
	 * 
	 * @param list
	 *            the current route list
	 * @param route
	 *            the route to be inserted
	 */
	private void insertRoute(ArrayList<Route> list, Route route) {
		boolean inserted = false;
		for (int i = 0; i < list.size(); i++) {
			if (list.get(i).getTotalTime() > route.getTotalTime()) {
				list.add(i,route);
				inserted=true;
				break;
			}
		}
		if (!inserted) {
			list.add(route);
		}
	}

	private String addTime(String time, int value) {
		int hrs = Integer.parseInt((time.substring(0, 2)));
		int min = Integer.parseInt((time.substring(2)));
		min += value;
		String resultString = "";
		if (min > 60) {
			hrs++;
			min -= 60;
		}
		if (min < 0) {
			hrs--;
			min += 60;
		}
		if (hrs < 10) {
			resultString += "0";
		}
		resultString += Integer.toString(hrs);
		if (min < 10) {
			resultString += "0";
		}
		resultString += Integer.toString(min);
		return resultString;
	}

	private int getTimeDifference(String a, String b) {
		int time;
		int aHrs = Integer.parseInt((a.substring(0, 2)));
		int aMin = Integer.parseInt((a.substring(2)));
		int bHrs = Integer.parseInt((b.substring(0, 2)));
		int bMin = Integer.parseInt((b.substring(2)));
		int hrs = bHrs - aHrs;
		int min = bMin - aMin;
		if (min < 0 && hrs == 0) {
			hrs--;
			min += 60;
		}
		time = 60 * hrs;
		time += min;
		return time;
	}

	

	/**
	 * Temporary main method running an example data set from a google docs
	 * spreadsheet to prove the concept of this algorithm.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		GraphConstructor gdb = new GraphConstructor("Oslo Lufthavn");
		RouteGenerator gdh = new RouteGenerator(gdb);
		SpreadsheetHandler sh = new SpreadsheetHandler();
		gdb.generateGraphFromRouteTable(sh.fetchInfo("http://spreadsheets.google.com/feeds/list/to0r8D9lbHK8hvcaewY_n1g/od6/public/values"));
		//gdh.printDB();
		gdh.printAllRoutes("Forskningsparken", "Oslo Lufthavn", "1900", 10);
		gdb.shutdown();

		System.exit(0);
	}

}
